package admin.core.mapper;

import admin.core.annotation.TableId;
import admin.core.enums.IdType;
import admin.core.enums.QueryType;
import admin.core.utils.AnnotationUtils;
import admin.core.utils.JdbcUtils;

import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.StringJoiner;

public class BaseMapper<T> {
    private final Class<T> entityClass = this.createEntityClass();

    private final String tableName;
    private final String idColumn;

    public int insert(T entity) {

        List<String> selectColumns = new ArrayList<>();
        List<Object> params = new ArrayList<>();

        List<Field> fieldList = AnnotationUtils.getAnnotatedFields(entityClass);
        for (Field field : fieldList) {
            field.setAccessible(true);
            if (field.isAnnotationPresent(TableId.class)) {
                TableId annotation = field.getAnnotation(TableId.class);
                IdType idType = annotation.type();
                if (idType == IdType.AUTO) {
                    continue;
                }
            }

            selectColumns.add(AnnotationUtils.getColumnValue(field));
            try {
                params.add(field.get(entity));
            } catch (IllegalAccessException e) {
                throw new RuntimeException(e);
            }
        }
        StringJoiner stringJoiner = new StringJoiner(",");
        for (int i = 0; i < params.size(); i++) {
            stringJoiner.add("?");
        }
        String sql = String.format("insert into %s( %s ) values( %s )", tableName, String.join(",", selectColumns), stringJoiner);

        return executeUpdate(sql, params.toArray());
    }

    public int deleteById(Object id) {
        String sql = String.format("delete from %s where %s = ?", tableName, idColumn);
        return executeUpdate(sql, id);
    }

    public int updateById(T entity) {

        List<String> updateColumns = new ArrayList<>();
        List<Object> params = new ArrayList<>();

        List<Field> fieldList = AnnotationUtils.getAnnotatedFields(entityClass);
        for (Field field : fieldList) {
            if (field.isAnnotationPresent(TableId.class)) {
                continue;
            }
            field.setAccessible(true);
            updateColumns.add(AnnotationUtils.getColumnValue(field) + " = ?");
            try {
                params.add(field.get(entity));
            } catch (IllegalAccessException e) {
                throw new RuntimeException(e);
            }
        }

        Field idField = AnnotationUtils.getIdField(entityClass);
        assert idField != null;
        idField.setAccessible(true);
        try {
            params.add(idField.get(entity));
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        }
        String sql = String.format("update %s set %s where %s = ?  ", tableName, String.join(",", updateColumns), idColumn);

        return executeUpdate(sql, params.toArray());
    }


    public BaseMapper() {
        tableName = AnnotationUtils.getTableNameValue(entityClass);
        idColumn = AnnotationUtils.getTableIdValue(entityClass);
    }

    public T selectById(Object id) {
        String sql = String.format("select * from %s where %s = ?", tableName, idColumn);

        List<T> list = executeQuery(sql, id);
        return list.isEmpty() ? null : list.get(0);
    }

    public List<T> selectList() {
        String sql = String.format("select * from %s", tableName);
        return executeQuery(sql);
    }

    public List<T> selectList(String text) {
        return selectList(text, 1, -1);
    }

    private Class<T> createEntityClass() {
        try {
            Type genericSuperclass = this.getClass().getGenericSuperclass();
            Type[] actualTypeArguments = ((ParameterizedType) genericSuperclass).getActualTypeArguments();
            if (actualTypeArguments.length < 1) {
                throw new RuntimeException("ServiceImpl does not specify a generic type.");
            }
            String typeName = actualTypeArguments[0].getTypeName();
            return (Class<T>) Class.forName(typeName);
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
    }

    public int executeUpdate(String sql, Object... args) {
        Connection con = null;
        PreparedStatement stmt = null;
        try {
            con = JdbcUtils.getConnection();
            stmt = con.prepareStatement(sql);
            for (int i = 0; i < args.length; i++) {
                stmt.setObject(i + 1, args[i]);
            }
            return stmt.executeUpdate();
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            JdbcUtils.close(null, stmt, con);
        }
    }

    public List<T> executeQuery(String sql, Object... args) {
        Connection con = null;
        PreparedStatement stmt = null;
        ResultSet rs = null;
        try {
            con = JdbcUtils.getConnection();
            stmt = con.prepareStatement(sql);
            List<T> list = new ArrayList<>();
            for (int i = 0; i < args.length; i++) {
                stmt.setObject(i + 1, args[i]);
            }
            rs = stmt.executeQuery();
            while (rs.next()) {
                T obj = rowToObject(rs);
                list.add(obj);
            }
            return list;
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            JdbcUtils.close(rs, stmt, con);
        }
    }


    public T rowToObject(ResultSet rs) throws Exception {
        T entity = entityClass.getDeclaredConstructor().newInstance();
        List<Field> fields = AnnotationUtils.getAnnotatedFields(entityClass);

        for (Field field : fields) {
            field.setAccessible(true);
            String columnValue = AnnotationUtils.getColumnValue(field);
            field.set(entity, rs.getObject(columnValue));
        }
        return entity;
    }


    public List<T> selectList(int pageNum, int pageSize) {
        String sql = String.format("select * from %s limit ?, ?", tableName);
        return executeQuery(sql, (pageNum - 1) * pageSize, pageSize);
    }

    public List<T> selectList(String text, Integer pageNum, Integer pageSize) {
        String sql = String.format("select * from %s ", tableName);

        StringBuilder sb = new StringBuilder(sql);
        List<Object> params = new ArrayList<>();
        if (!text.isEmpty()) {
            List<Map<String, Object>> searchFields = AnnotationUtils.getSearchFields(entityClass);
            for (int i = 0; i < searchFields.size(); i++) {
                Map<String, Object> field = searchFields.get(i);
                boolean isFirst = i == 0;

                if (isFirst) {
                    sb.append("where ");
                } else {
                    sb.append(" or ");
                }

                sb.append(field.get("column"));
                QueryType type = (QueryType) field.get("type");
                if (type == QueryType.EQUALS) {
                    sb.append(" = ?");
                    params.add(text);
                } else {
                    sb.append(" like ?");
                    params.add("%" + text + "%");
                }
            }
        }
        if (pageSize != -1) {
            sb.append(" limit ?, ? ");
            params.add((pageNum - 1) * pageSize);
            params.add(pageSize);
        }

        return executeQuery(sb.toString(), params.toArray());
    }
}